#include "Functions.h"

int main(int argc, char **argv) {
    //info: vetor que determina quantas linhas da matriz um no vai receber(info[0]) e a posicao do elemento da diagonal principal(info[1])
    //mylines: matriz que cada no vai receber. Essa matriz contem os elementos da matrizA, limitada pelo numero de linhas que o no vai receber
    //amount_lines: quantidade de linhas da matrizA que cada no vai receber
    //msg: variavel que indica se eh para fazer outra iteracao ou nao
    //pos_element: posicao do elemento da diagonal principal correspondente a linha da matriz
    int rc, myrank, n_procs, amount_lines, aux_amount, info[2], pos_element, msg;
    double *mylines, *linesproc, total;
    int i, j, k;
    struct timeval *begin;
	char hostname[256];

    omp_set_num_threads(1);
    if(argc != 2) {
        printf("Usage: %s <file>\n", argv[0]);
        return EXIT_FAILURE;
    }
	begin = malloc(sizeof(struct timeval));
	gettimeofday(begin, NULL);
    rc = MPI_Init(&argc, &argv);
    if(rc != MPI_SUCCESS) {
        printf("Error starting MPI program. Aborting...\n");
        MPI_Abort(MPI_COMM_WORLD, rc);
    }
    MPI_Comm_size(MPI_COMM_WORLD, &n_procs);
    MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
    if(myrank == 0) {   //o no mestre executa esse bloco de codigo
        double *vectorB_k;
        double *vectorB_k1;
        double **A, *bPrlo;
        double error;
		struct timeval *end;

        if(ReadFile(argv[1]))
            return EXIT_FAILURE;
        if(AllocateData(n_procs))
            return EXIT_FAILURE;
        if(ManipulateData())
            return EXIT_FAILURE;
        SetParameters(n_procs);
		for(i = 1; i < n_procs; i++) {
			MPI_Ssend(&file->J_ORDER, 1, MPI_INT, i, 0, MPI_COMM_WORLD); //avisa a todos os processos o numero de elementos do vetorB, e consequentemente o numero de elementos da linha da matrizA
			MPI_Send(file->vectorB, file->J_ORDER, MPI_DOUBLE, i, 0, MPI_COMM_WORLD); 	//envia a todos os processos o vetorB jah normalizado
		}
        //o no mestre pega para ele as primeiras linhas da matrizA
        amount_lines = GetAmountLines();
        aux_amount = amount_lines;
        mylines = malloc(amount_lines*file->J_ORDER*sizeof(double));
        GetLines(mylines, amount_lines, &pos_element);
        linesproc = malloc((file->LINES_PER_PROC+1)*file->J_ORDER*sizeof(double)); //aloca espaco para as linhas da matrizA que vao ser enviadas a outros nos

        bPrlo = malloc(amount_lines*sizeof(double));
        for(i = 1; i < n_procs; i++) {      //o no mestre distribui as linhas da matrizA entre os nos
            amount_lines = GetAmountLines();
            GetLines(linesproc, amount_lines, &pos_element);
            info[0] = amount_lines;
            info[1] = pos_element;      //variavel que determina qual eh a posicao do elemento da diagonal principal
            MPI_Ssend(info, 2, MPI_INT, i, 0, MPI_COMM_WORLD);     //envia para cada no a quantidade de linhas que ele vai receber bem como a posicao do elemento da diagonal principal
            MPI_Send(linesproc, amount_lines*file->J_ORDER, MPI_DOUBLE, i, 0, MPI_COMM_WORLD);    //envia as linhas da matrizA para cada no a
        }

        vectorB_k = malloc(file->J_ORDER*sizeof(double));   //valores de x da iteracao atual
        vectorB_k1 = malloc(file->J_ORDER*sizeof(double));  //valores de x da proxima iteracao
        A = malloc(aux_amount*sizeof(double*));
        for(i = 0; i < aux_amount; i++)
            A[i] = malloc(file->J_ORDER*sizeof(double));
        for(i = 0; i < file->J_ORDER; i++) {    //os primeiros valores de x eh o vetorB inicial para poder comparar com os valores de x da proxima iteracao
            vectorB_k[i] = file->vectorB[i];
        }
        k = 0;
        for(i = 0; i < aux_amount; i++) {
            for(j = 0; j < file->J_ORDER; j++) {
                A[i][j] = mylines[k++];
            }
        }
        file->iterations = 0;
        msg = 1;    //se mudar para 0 quer dizer que nao precisa calcular os proximos valores de x pq a condicao foi atingida
        do {
            file->iterations++;
            #pragma omp parallel for private(k) shared(bPrlo,vectorB_k1,vectorB_k)
            for(i = 0; i < aux_amount; i++)
            {
                bPrlo[i] = file->vectorB[i];

                for (k = 0; k < i; k++)
                {
                    bPrlo[i] -= vectorB_k[k] * A[i][k];
                }

                for (k = i + 1; k < file->J_ORDER; k++)
                {
                    bPrlo[i] -= vectorB_k[k] * A[i][k];
                }
            }

            for(i = 0; i < aux_amount; i++)
                vectorB_k1[i] = bPrlo[i];
            MPI_Gatherv(NULL, 0, MPI_DOUBLE, vectorB_k1, recvcount, displs, MPI_DOUBLE, 0, MPI_COMM_WORLD);
            CalculateError(vectorB_k, vectorB_k1, &error);  //calcula o erro e verifica se convergiu
            if((file->iterations >= file->J_ITE_MAX) || (error <= file->J_ERROR)) {
                msg = 0;
                for(j = 1; j < n_procs; j++)
                    MPI_Ssend(&msg, 1, MPI_INT, j, 1, MPI_COMM_WORLD);
            }
            else {
                for(j = 1; j < n_procs; j++) {
                    MPI_Ssend(&msg, 1, MPI_INT, j, 1, MPI_COMM_WORLD);
			        MPI_Send(vectorB_k1, file->J_ORDER, MPI_DOUBLE, j, 0, MPI_COMM_WORLD); 	//envia o vetorB novamente para os nos calcularem os proximos valores de x
                }
                for(i = 0; i < file->J_ORDER; i++) {
                    vectorB_k[i] = vectorB_k1[i];
                }
            }
        } while(msg == 1);

    end = malloc(sizeof(struct timeval));
	gettimeofday(end, NULL);

    printf("Iterations: %d\n", file->iterations);
    total = 0;
    for(i = 0; i < file->J_ORDER; i++)
        total += file->line_test[i]*vectorB_k1[i];
    printf("Valor aproximado utilizando a linha %d: B[%d] = %lf\nValor real = %lf\n", file->J_ROW_TEST, file->J_ROW_TEST, total, file->line_test[file->J_ORDER]);
	ShowElapsedTime(begin, end);

    } else if(myrank > 0) {    //os nos escravos executarao esse bloco de codigo
        //num_elements: numero de elementos que cada linha da matrizA
        //status: variavel associada a informacoes de recebimento da mensagem enviada pelo no mestre
        //count: numero de elementos que foram recebidos da mensagem
        MPI_Status status;
        int num_elements = 0, count, l;
        double *vectorB, *x0, *x1, **A, *bPrlo;

        MPI_Recv(&num_elements, 1, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);     //recebe, do no mestre,  a ordem da matrizA que na verdade eh a quantidade de elementos de qualquer linha da matriz
        if(num_elements == 0) {
            printf("Proc %d nao recebeu o numero de elementos do no mestre!\n", myrank);
            return EXIT_FAILURE;
        }
        vectorB = malloc(num_elements*sizeof(double));

        MPI_Recv(vectorB, num_elements, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &status);   //recebe o vetorB inicial(x iniciais)
        MPI_Get_count(&status, MPI_DOUBLE, &count);
        if(count != num_elements) {
            printf("Proc %d nao recebeu todos os elementos do vetorB do no mestre!\n", myrank);
            return EXIT_FAILURE;
        }
        MPI_Recv(info, 2, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); //recebe o vetor info
        MPI_Get_count(&status, MPI_INT, &count);
        if(count != 2) {
            printf("Proc %d nao recebeu todos os elementos do vetor info do no mestre!\n", myrank);
            return EXIT_FAILURE;
        }
        mylines = malloc(info[0]*num_elements*sizeof(double));
        MPI_Recv(mylines, info[0]*num_elements, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD, &status); //recebe as linhas da matrizA
        MPI_Get_count(&status, MPI_DOUBLE, &count);
        if(count != info[0]*num_elements) {
            printf("Proc %d nao recebeu todos os elementos das linhas da matrizA do no mestre!\n", myrank);
            return EXIT_FAILURE;
        }
        bPrlo = malloc(num_elements*sizeof(double));
        x0 = malloc(num_elements*sizeof(double));
        x1 = malloc(num_elements*sizeof(double));
        A = malloc(num_elements*sizeof(double*));
        for(i = 0; i < num_elements; i++)
            A[i] = malloc(num_elements*sizeof(double));
        k = 0;
        for(i = info[1]; i < info[1]+info[0]; i++) {
            for(j = 0; j < num_elements; j++) {
                A[i][j] = mylines[k++];
            }
        }
        for(i = 0; i < num_elements; i++) {
            x0[i] = vectorB[i];
        }
        j = 0;
        do {
            #pragma omp parallel for private(k) shared(bPrlo,x1,x0)
            for (i = info[1]; i < info[1]+info[0]; i++)
            {
                bPrlo[i] = vectorB[i];

                for (k = 0; k < i; k++)
                {
                    bPrlo[i] -= x0[k] * A[i][k];
                }

                for (k = i + 1; k < num_elements; k++)
                {
                    bPrlo[i] -= x0[k] * A[i][k];
                }
            }

            MPI_Gatherv(&bPrlo[info[1]], info[0], MPI_DOUBLE, NULL, NULL, NULL, MPI_DOUBLE, 0, MPI_COMM_WORLD);
            MPI_Recv(&msg, 1, MPI_INT, 0, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);  //recebe msg e verifica se eh para continuar as iteracoes
            if(msg) {
                MPI_Recv(x0, num_elements, MPI_DOUBLE, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
                MPI_Get_count(&status, MPI_DOUBLE, &count);
                if(count != num_elements) {
                    printf("Proc %d nao recebeu todos os elementos do vetorB do no mestre!\n", myrank);
                    return EXIT_FAILURE;
                }
            }
            else {
                msg = 0;
            }

        } while(msg == 1);

    }
    MPI_Finalize();

    return EXIT_SUCCESS;

}
